﻿
using System.Globalization;
using System.Linq;
using System.Xml.Linq;
using DotNetNeural.Data.Clusters;
using DotNetNeural.Data.Learning;
using System;
namespace DotNetNeural.Data.Utils
{
    public class SerializationUtils
    {
        public const string ArrayPrefix = "vec";

        public XElement ToXml(object obj, Type type)
        {
            if (obj == null && type == null)
                throw new NullReferenceException();

            if (obj.GetType() != type)
                throw new InvalidCastException();

            if (type == typeof(float[]))
                return ToXml((float[])obj);

            if (type == typeof(Cluster))
                return ToXml((Cluster)obj);

            if (type == typeof(TrainingSet))
                return ToXml((TrainingSet)obj);

            if (type == typeof(TrainingSetItem))
                return ToXml((TrainingSetItem)obj);

            return null;
        }

        public object FromXml(string str, Type type)
        {
            if (type == typeof(float[]))
                return VectorFromXml(str);

            if ( type == typeof(Cluster))
                return ClusterFromXml(str);

            if (type == typeof(TrainingSet))
                return TrainingSetFromXml(str);

            if (type == typeof(TrainingSetItem))
                return TrainingSetItemFromXml(str);

            return null;
        }

        #region Private methods

        private XElement ToXml(float[] vec)
        {
            XElement root = new XElement(ArrayPrefix);

            foreach (var c in vec)
            {
                root.Add(
                    new XElement("c", GetString(c))
                    );
            }

            return root;
        }

        private XElement ToXml(Cluster cluster)
        {
            XElement root = new XElement("cluster",
                new XAttribute("dispersion", GetString(cluster.Dispersion)));

            root.Add(
                new XElement("cluster_center", ToXml(cluster.Center)));

            XElement itemsElement = new XElement("items");
            root.Add(itemsElement);

            foreach (float[] v in cluster)
            {
                itemsElement.Add(ToXml(v));
            }

            return root;
        }

        private XElement ToXml(TrainingSetItem item)
        {
            return new XElement("item",
                ToXml(item.Input), ToXml(item.Output));
        }

        private XElement ToXml(TrainingSet set)
        {
            XElement root = new XElement("training_set",
                new XAttribute("inputs", set.InputsCount),
                new XAttribute("outputs", set.OutputsCount)
                );

            XElement learningItems = new XElement("learning_items");
            XElement controlItems = new XElement("control_items");
            root.Add(learningItems, controlItems);

            foreach (var i in set.LearningItems)
            {
                learningItems.Add(ToXml(i));
            }

            foreach (var i in set.ControlItems)
            {
                controlItems.Add(ToXml(i));
            }

            return root;
        }

        private TrainingSetItem TrainingSetItemFromXml(string xml)
        {
            XElement e = XElement.Parse(xml);

            var items = e.Descendants(ArrayPrefix);

            float[] item1 = VectorFromXml(items.First().ToString());
            float[] item2 = VectorFromXml(items.Skip(1).First().ToString());

            return new TrainingSetItem(item1, item2);
        }

        private TrainingSet TrainingSetFromXml(string xml)
        {
            XElement root = XElement.Parse(xml);

            int inputsCount = Int32.Parse(root.Attribute("inputs").Value);
            int outputsCount = Int32.Parse(root.Attribute("outputs").Value);

            TrainingSet set = new TrainingSet(inputsCount, outputsCount);

            var learningItems = root.Descendants("learning_items");
            foreach (var l in learningItems.Descendants("item"))
            {
                set.AddItem(TrainingSetItemFromXml(l.ToString()), false);
            }

            var controlItems = root.Descendants("control_items");
            foreach (var c in controlItems.Descendants("item"))
            {
                set.AddItem(TrainingSetItemFromXml(c.ToString()), true);
            }

            return set;
        }

        private float[] VectorFromXml(string xml)
        {
            XDocument doc = XDocument.Parse(xml);

            int size = doc.Descendants("c").Count();

            float[] result = new float[size];

            int index = 0;
            foreach (var e in doc.Descendants("c"))
            {
                result[index] = (GetFloat(e.Value));
                ++index;
            }

            return result;
        }

        private Cluster ClusterFromXml(string xml)
        {
            XDocument doc = XDocument.Parse(xml);

            XElement root = doc.Descendants("cluster").FirstOrDefault();
            float dispersion = GetFloat(root.Attribute("dispersion").Value);

            XElement centerElement = doc.Descendants("cluster_center").FirstOrDefault();

            float[] clusterCenter = VectorFromXml(centerElement.ToString());

            Cluster result = new Cluster(clusterCenter)
            {
                Dispersion = dispersion
            };

            XElement itemsElement = doc.Descendants("items").FirstOrDefault();

            foreach (var vecElement in itemsElement.Descendants("vec"))
            {
                result.Add(VectorFromXml(vecElement.ToString()));
            }

            return result;
        }

        private float GetFloat(string str)
        {
            return float.Parse(str, GetNumberFormat());
        }

        private string GetString(float f)
        {
            return f.ToString(GetNumberFormat());
        }

        private NumberFormatInfo GetNumberFormat()
        {
            return CultureInfo.InvariantCulture.NumberFormat;
        }

        #endregion
    }
}
